package manager

import (
	"fmt"
	"sync"
)

var QueueEmptyErr = fmt.Errorf("queue empty")
var QueueFullErr = fmt.Errorf("queue full")

type Queue struct {
	q    []string
	s, e int
	len  int
}

func (q *Queue) Push(s string) error {
	if (q.e+1)%q.len == q.s {
		return QueueFullErr
	}
	q.q[q.e] = s
	q.e = (q.e + 1) % q.len
	return nil
}

func (q *Queue) Pop() (ret string, e error) {
	if q.s == q.e {
		return "", QueueEmptyErr
	}
	ret = q.q[q.s]
	q.s = (q.s + 1) % q.len
	return
}

func NewQueue(size int) *Queue {
	return &Queue{
		len: size,
		q:   make([]string, size),
	}
}

type PreviewerManager struct {
	sync.Mutex
	allocate map[string]string // jsBundleId -> perviewerId
	idle     *Queue
	cfg      Config
}

func NewPreviewerManager(cfg Config) *PreviewerManager {
	return &PreviewerManager{
		allocate: make(map[string]string),
		// idle:     NewQueue(cfg.PreviewerPoolSize + 1),
		Mutex: sync.Mutex{},
		cfg:   cfg,
	}
}

func (pm *PreviewerManager) AllocatePreviewer(sessionId string) (previewerD PreviewerDescriptor, e error) {
	pm.Lock()
	defer pm.Unlock()
	previewerD.Namespace = pm.cfg.PreviewerPoolNamespace

	allcKey := sessionId

	if previewerId, ok := pm.allocate[allcKey]; ok {
		previewerD.PreviewerId = previewerId
		return
	}

	// 分配新的previewer
	previewerD.PreviewerId, e = pm.idle.Pop()
	if e != nil {
		if e == QueueEmptyErr {
			// TODO: expand pool
		}
		return
	}

	pm.allocate[allcKey] = previewerD.PreviewerId
	return
}

func (pm *PreviewerManager) ReleasePreviewer(sessionId, previewerId string) error {
	pm.Lock()
	defer pm.Unlock()

	allcKey := sessionId

	if _pId, ok := pm.allocate[allcKey]; !ok {
		return fmt.Errorf("sessionId %v no in bind", allcKey)
	} else if _pId != previewerId {
		return fmt.Errorf("invalid release pair (%v, %v)", allcKey, previewerId)
	}

	delete(pm.allocate, allcKey)
	return pm.idle.Push(previewerId)
}

func (pm *PreviewerManager) CheckBinding(sessionId, previewerId string) error {
	pm.Lock()
	defer pm.Unlock()
	allcKey := sessionId

	if _pId, ok := pm.allocate[allcKey]; !ok {
		return fmt.Errorf("allcKey %v no allocate", allcKey)
	} else if _pId != previewerId {
		return fmt.Errorf("invalid pair (%v, %v)", allcKey, previewerId)
	}
	return nil
}

// TODO: add pool auto-scale

// TODO: add previewer bind timeout
